//
//  MenuMeterCPUStats.m
//
// 	Reader object for CPU information and load
//
//	Copyright (c) 2002-2006 Alex Harper
//
// 	This file is part of MenuMeters.
// 
// 	MenuMeters is free software; you can redistribute it and/or modify
// 	it under the terms of the GNU General Public License as published by
// 	the Free Software Foundation; either version 2 of the License, or
// 	(at your option) any later version.
// 
// 	MenuMeters is distributed in the hope that it will be useful,
// 	but WITHOUT ANY WARRANTY; without even the implied warranty of
// 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// 	GNU General Public License for more details.
// 
// 	You should have received a copy of the GNU General Public License
// 	along with MenuMeters; if not, write to the Free Software
// 	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
// 

#import "MenuMeterCPUStats.h"
#import "smc.h"

@implementation MenuMeterCPUStats

///////////////////////////////////////////////////////////////
//	
//	init/dealloc
//
///////////////////////////////////////////////////////////////

extern io_connect_t conn;
SMCVal_t      val;

- (id)init {
	// Length param for sysctl
	size_t 						length;
	// MIB block for sysctl
	int							mib[2];
	// Clockspeed
	UInt32						clockRate;
	// Errors
	kern_return_t				kStatus;
	// host_info params
	unsigned int				processorCount;
	processor_cpu_load_info_t	processorTickInfo;
	mach_msg_type_number_t		processorMsgCount;
	// loops
	unsigned int				i, j;
	// Temp formatter we are using to set up a formatter
	NSNumberFormatter			*tempFormat;
				
	// Allow super to init
	self = [super init];
	if (!self) {
		return nil;
	}
	
	// Gather the pretty name
	theCPUName = [self _cpuPrettyName];
	[theCPUName retain];

	// Set up a NumberFormatter for localization. This is based on code contributed by Mike Fischer
	// (mike.fischer at fi-works.de) for use in MenuMeters.
	// We have to do this early so we can use the resulting format on the GHz processor string
	tempFormat = [[NSNumberFormatter alloc] init];
	[tempFormat setLocalizesFormat:YES];
	[tempFormat setFormat:@"0.00"];
	// Go through an archive/unarchive cycle to work around a bug on pre-10.2.2 systems
	// see http://cocoa.mamasam.com/COCOADEV/2001/12/2/21029.php
	twoDigitFloatFormatter = [[NSUnarchiver unarchiveObjectWithData:[NSArchiver archivedDataWithRootObject:tempFormat]] retain];
	// Dump the temp
	[tempFormat release];
	
	// Gather the clock rate string
	length = sizeof(clockRate);
	mib[0] = CTL_HW;
	mib[1] = HW_CPU_FREQ;
	sysctl(mib, 2, &clockRate, &length, NULL, 0);
	if (clockRate > 1000000000) {
		theClockSpeed = [NSString stringWithFormat:@"%@GHz",
							[twoDigitFloatFormatter stringForObjectValue:
								[NSNumber numberWithDouble:(double)clockRate / 1000000000]]];
	}
	else {
		theClockSpeed = [NSString stringWithFormat:@"%dMHz", clockRate / 1000000];
	}
	[theClockSpeed retain];
	
	// Gather the cpu count
	length = sizeof(theCPUCount);
	mib[0] = CTL_HW;
	mib[1] = HW_NCPU;	
	sysctl(mib, 2, &theCPUCount, &length, NULL, 0);
	
	// Set up our mach host and default processor set for later calls
	theHost = mach_host_self();
	processor_set_default(theHost, &theProcessorSet);
	
	SMCOpen(&conn);

	// Build the storage for the prior ticks and store the first block of data
	kStatus = host_processor_info(theHost, PROCESSOR_CPU_LOAD_INFO, &processorCount, 
									(processor_info_array_t *)&processorTickInfo, &processorMsgCount);
	if (kStatus != KERN_SUCCESS) {
		return nil;
	}
	priorCPUTicks = malloc(processorCount * sizeof(*priorCPUTicks));
	for (i = 0; i < processorCount; i++) {
		for (j = 0; j < CPU_STATE_MAX; j++) {
			priorCPUTicks[i].cpu_ticks[j] = processorTickInfo[i].cpu_ticks[j];
		}
	}
	vm_deallocate(mach_task_self(), (vm_address_t)processorTickInfo, (vm_size_t)(processorMsgCount * sizeof(*processorTickInfo)));
	
	// Localizable strings load
	localizedStrings = [[NSDictionary dictionaryWithObjectsAndKeys:
							[[NSBundle bundleForClass:[self class]] localizedStringForKey:kProcessorNameFormat value:nil table:nil],
							kProcessorNameFormat,
							[[NSBundle bundleForClass:[self class]] localizedStringForKey:kTaskThreadFormat value:nil table:nil],
							kTaskThreadFormat,
							[[NSBundle bundleForClass:[self class]] localizedStringForKey:kLoadAverageFormat value:nil table:nil],
							kLoadAverageFormat,
							[[NSBundle bundleForClass:[self class]] localizedStringForKey:kTemperatureFirstProcFormat value:nil table:nil],
							kTemperatureFirstProcFormat,
							[[NSBundle bundleForClass:[self class]] localizedStringForKey:kTemperatureSubsequentProcFormat value:nil table:nil],
							kTemperatureSubsequentProcFormat,
							[[NSBundle bundleForClass:[self class]] localizedStringForKey:kNoInfoErrorMessage value:nil table:nil],
							kNoInfoErrorMessage,
							nil] retain];
	// Send on back
	return self;
	
} // init

- (void)dealloc {
	
	// Release cached data
	[theCPUName release];
	[theClockSpeed release];
	
	// Release the prior tick array
	free(priorCPUTicks);
	
	// Release the load average format localizer
	[twoDigitFloatFormatter release];
	
	// Release localize strings
	[localizedStrings release];
	
	[super dealloc];

} // dealloc

///////////////////////////////////////////////////////////////
//	
//	CPU info
//
///////////////////////////////////////////////////////////////

- (NSString *)cpuName {
	
	return theCPUName;

} // cpuName

- (NSString *)cpuSpeed {
	
	return theClockSpeed;

} // cpuSpeed

- (UInt32)numberOfCPUs {
	
	return theCPUCount;

} // numberOfCPUs

- (NSString *)processorDescription {
	
	return [NSString stringWithFormat:[localizedStrings objectForKey:kProcessorNameFormat], 
				[self numberOfCPUs], [self cpuName], [self cpuSpeed]];

} // processorDescription

- (NSString *)currentTemp {
	// Errors
	kern_return_t				kStatus;
	// Values from host_processor_info
	unsigned int				procCount, tempCount;
	int							*tempArray;
	// Loop
	int							i;
	int							result;
	// Return val
	NSString					*tempString = nil;
	char						key[32]; 

	i = 0;
	sprintf(key, "TC%dD", i);
	result = SMCReadKey2(key, &val,conn);
	
	float c_temp= ((val.bytes[0] * 256 + val.bytes[1]) >> 2)/64;
	
	
	tempString = [NSString stringWithFormat:[NSString stringWithCString:"%f"], c_temp];
	return tempString;
	
	kStatus = host_processor_info(theHost, PROCESSOR_TEMPERATURE, &procCount, &tempArray, &tempCount);
	if (kStatus == KERN_SUCCESS) {
		if (tempArray[0] >= 0) {
			for (i = 0; i < procCount; i++) {
				if (i == 0 && !tempString) {
					tempString = [NSString stringWithFormat:[localizedStrings objectForKey:kTemperatureFirstProcFormat],
						(int)(tempArray[i] * 1.8 + 32), tempArray[i]];
				}
				else {
					tempString = [tempString stringByAppendingString:
									[NSString stringWithFormat:[localizedStrings objectForKey:kTemperatureSubsequentProcFormat],
									(int)(tempArray[i] * 1.8 + 32), tempArray[i]]];
				}
			}
		}
		vm_deallocate(mach_task_self(), (vm_address_t)tempArray, (vm_size_t)(tempCount * sizeof(int)));
		return tempString;
	}
	else {
		return nil;
	}
} // currentTemp

///////////////////////////////////////////////////////////////
//	
//	Load info
//
///////////////////////////////////////////////////////////////

- (NSString *)currentProcessorTasks {
	// Errors
	kern_return_t						kStatus;
	// Data
	struct processor_set_load_info		loadInfo;
	unsigned int						count = PROCESSOR_SET_LOAD_INFO_COUNT;
	// Return
	NSString							*theTasks;
	
	// Fetch
	kStatus = processor_set_statistics(theProcessorSet, PROCESSOR_SET_LOAD_INFO, (processor_set_info_t)&loadInfo, &count);
	if (kStatus != KERN_SUCCESS) {
		theTasks = [localizedStrings objectForKey:kNoInfoErrorMessage];
	}
	else {
		theTasks = [NSString stringWithFormat:[localizedStrings objectForKey:kTaskThreadFormat],
			loadInfo.task_count, loadInfo.thread_count];
	}
	
	return theTasks;
			
} // currentProcessorTasks

- (NSString *)loadAverage {
	// Data
	double								loads[3];
 	// Return
	NSString							*loadAverage;
	
	// Fetch using getloadavg() to better match top, from Michael Nordmeyer (http://goodyworks.com)
	if (getloadavg(loads, 3) == -1) {
		loadAverage = [localizedStrings objectForKey:kNoInfoErrorMessage];
	}
	else {
		loadAverage = [NSString stringWithFormat:[localizedStrings objectForKey:kLoadAverageFormat], 
			[twoDigitFloatFormatter stringForObjectValue:
				[NSNumber numberWithFloat:(float)loads[0]]],
			[twoDigitFloatFormatter stringForObjectValue:
				[NSNumber numberWithFloat:(float)loads[1]]],
			[twoDigitFloatFormatter stringForObjectValue:
				[NSNumber numberWithFloat:(float)loads[2]]]];
	}

	return loadAverage;
	
} // loadAverage

- (NSArray *)currentLoad:(BOOL)averageAllProcs {
	// host_info params
	unsigned int				processorCount;
	processor_cpu_load_info_t	processorTickInfo;
	mach_msg_type_number_t		processorMsgCount;
	// Errors
	kern_return_t				kStatus;
	// Loops
	unsigned int				i, j;
	// Data per proc
	unsigned long				system, user, nice, idle;
	unsigned long long			total, totalnonice;
	// Data average for all procs
	unsigned long long			systemall = 0;
	unsigned long long			userall = 0;
	unsigned long long			niceall = 0;
	unsigned long long			idleall = 0;
	unsigned long long			totalall = 0;
	unsigned long long			totalallnonice = 0;
	// Return data
	NSMutableArray				*loadInfo;

	// Read the current ticks
	kStatus = host_processor_info(theHost, PROCESSOR_CPU_LOAD_INFO, &processorCount, 
									(processor_info_array_t *)&processorTickInfo, &processorMsgCount);
	if (kStatus == KERN_SUCCESS) {
		// We have valid info so build the needed mutable array
		if (averageAllProcs) {
			loadInfo = [NSMutableArray arrayWithCapacity:1];
		}
		else {
			loadInfo = [NSMutableArray arrayWithCapacity:processorCount];
		}
		
		// Loop the processors
		for (i = 0; i < processorCount; i++) {
			
			// Calc load types and totals, with guards against overflows
			if (processorTickInfo[i].cpu_ticks[CPU_STATE_SYSTEM] >= priorCPUTicks[i].cpu_ticks[CPU_STATE_SYSTEM]) {
				system = processorTickInfo[i].cpu_ticks[CPU_STATE_SYSTEM] - priorCPUTicks[i].cpu_ticks[CPU_STATE_SYSTEM];
			}
			else {
				system = processorTickInfo[i].cpu_ticks[CPU_STATE_SYSTEM] + (ULONG_MAX - priorCPUTicks[i].cpu_ticks[CPU_STATE_SYSTEM] + 1);
			}
			if (processorTickInfo[i].cpu_ticks[CPU_STATE_USER] >= priorCPUTicks[i].cpu_ticks[CPU_STATE_USER]) {
				user = processorTickInfo[i].cpu_ticks[CPU_STATE_USER] - priorCPUTicks[i].cpu_ticks[CPU_STATE_USER];
			}
			else {
				user = processorTickInfo[i].cpu_ticks[CPU_STATE_USER] + (ULONG_MAX - priorCPUTicks[i].cpu_ticks[CPU_STATE_USER] + 1);
			}
			if (processorTickInfo[i].cpu_ticks[CPU_STATE_NICE] >= priorCPUTicks[i].cpu_ticks[CPU_STATE_NICE]) {
				nice = processorTickInfo[i].cpu_ticks[CPU_STATE_NICE] - priorCPUTicks[i].cpu_ticks[CPU_STATE_NICE];
			}
			else {
				nice = processorTickInfo[i].cpu_ticks[CPU_STATE_NICE] + (ULONG_MAX - priorCPUTicks[i].cpu_ticks[CPU_STATE_NICE] + 1);
			}
			if (processorTickInfo[i].cpu_ticks[CPU_STATE_IDLE] >= priorCPUTicks[i].cpu_ticks[CPU_STATE_IDLE]) {
				idle = processorTickInfo[i].cpu_ticks[CPU_STATE_IDLE] - priorCPUTicks[i].cpu_ticks[CPU_STATE_IDLE];
			}
			else {
				idle = processorTickInfo[i].cpu_ticks[CPU_STATE_IDLE] + (ULONG_MAX - priorCPUTicks[i].cpu_ticks[CPU_STATE_IDLE] + 1);
			}
			total = system + user + nice + idle;
			totalnonice = system + user + idle;
			
			// Update avgs
			systemall += system;
			userall += user;
			niceall += nice;
			idleall += idle;
			totalall += total;
			totalallnonice += totalnonice;
			
			// Sanity
			if (total < 1) {
				total = 1;
			}
			if (totalnonice < 1) {
				totalnonice = 1;
			}
			
			// Store here only if we are going proc by proc
			if (!averageAllProcs) {
				[loadInfo addObject:[NSDictionary dictionaryWithObjectsAndKeys:
													[NSNumber numberWithDouble:((double)system / total)], @"system",
													[NSNumber numberWithDouble:((double)user / total)], @"user",
													[NSNumber numberWithDouble:((double)nice / total)], @"nice",
													[NSNumber numberWithDouble:((double)system / totalnonice)], @"systemwithoutnice",
													[NSNumber numberWithDouble:((double)user / totalnonice)], @"userwithoutnice",
													nil]];
			}
		}
		
		// Build a data block for averages only if thats what's required
			if (averageAllProcs) {
			// Sanity
			if (totalall < 1) {
				totalall = 1;
			}
			if (totalallnonice < 1) {
				totalallnonice = 1;
			}
			[loadInfo addObject:[NSDictionary dictionaryWithObjectsAndKeys:
												[NSNumber numberWithDouble:((double)systemall / totalall)], @"system",
												[NSNumber numberWithDouble:((double)userall / totalall)], @"user",
												[NSNumber numberWithDouble:((double)niceall / totalall)], @"nice",
												[NSNumber numberWithDouble:((double)systemall / totalallnonice)], @"systemwithoutnice",
												[NSNumber numberWithDouble:((double)userall / totalallnonice)], @"userwithoutnice",
												nil]];
		}
		
		// Copy the new data into previous	
		for (i = 0; i < processorCount; i++) {
			for (j = 0; j < CPU_STATE_MAX; j++) {
				priorCPUTicks[i].cpu_ticks[j] = processorTickInfo[i].cpu_ticks[j];
			}
		}
		
		// Dealloc
		vm_deallocate(mach_task_self(), (vm_address_t)processorTickInfo, (vm_size_t)(processorMsgCount * sizeof(*processorTickInfo)));
		
		// Send the gathered data back
		return loadInfo;		
	}
	else {
		// On bad data just return nothing
		return nil;
	}

} // currentLoad

///////////////////////////////////////////////////////////////
//	
//	Utility
//
///////////////////////////////////////////////////////////////

- (NSString *)_cpuPrettyName {
	// Used to read processor name
	const NXArchInfo			*archInfo;
	// Gestalt return
	long						gestVal = 0;
	OSErr						err = 0;
	// The name
	NSString					*prettyName = @"Unknown CPU";
	
	// Gather the pretty name (Aquamon demonstrated this)
	archInfo = NXGetLocalArchInfo();
	if (archInfo) {
		prettyName = [NSString stringWithCString:archInfo->description];
	}
	
	// Now try to do better for 7455 Apollo, 7447 AlBooks, and Sahara G3s.
	// Note that this still doesn't work for some 7455s in 10.2.
	// Since those same machines return the correct type in Classic
	// I'm assuming its an Apple bug.
	err = Gestalt(gestaltNativeCPUtype, &gestVal);
	if (err == noErr) {
		if (gestVal == gestaltCPUApollo) {
			prettyName = @"PowerPC 7455";
		}
		// Gestalt says 7447, but CHUD says 7457. Let's believe CHUD.
		// Patch from Alex Eddy
		else if (gestVal == gestaltCPUG47447) {
			prettyName = @"PowerPC 7457";
		}
		else if (gestVal == gestaltCPU750FX) {
			prettyName = @"PowerPC 750fx"; 
		}
	}
	return prettyName;

} // _cpuPrettyName

@end
